/*
 * Copyright (C) 2010-2012 Enrique Lara (enrique.posix2.lara@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package app.transitalarm.model;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;

import android.content.Context;
import android.content.Intent;

import app.transitalarm.PollingService;
import app.transitalarm.R;
import app.transitalarm.Settings;

import app.transitalarm.util.Constants;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;

public final class Poller implements Runnable {
    private static final int OFF = 0;
    private static final int GREEN = 0xFF00FF00;
    private static final int RED = 0xFFFF0000;

    /**
     * Number of Consecutive Socket Timeouts before calling it quits.
     */
    private static final int MAX_CONSECUTIVE_TIMEOUTS = 3;
    private static final int MAX_ERRORS = 2;
    private final Context context;
    private final Alarm a;
    private final long sleepMillis;
    private final long endTimeMillis;
    private final int maxAttempts;
    private volatile Thread runner;
    private final NotificationManager notificationMgr;
    private final PendingIntent viewPendingIntent;

    public synchronized void startThread() {
        if (runner == null) {
            runner = new Thread(this);
            runner.setDaemon(true);
            runner.start();
        }
    }

    public synchronized void stopThread() {
        if (runner != null) {
            final Thread moribund = runner;
            runner = null;
            moribund.interrupt();
            notificationMgr.cancel(a.getId());
        }
    }

    public Poller(final Alarm a, final Context c, final boolean oneOff) {
        final Calendar cal = Calendar.getInstance();

        this.a = a;
        context = c;

        notificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        final Intent viewIntent = new Intent(Constants.ACTION_VIEW, a.getUri());
        viewPendingIntent = PendingIntent.getService(context, 0, viewIntent, 0);

        final int refreshRateSeconds = Settings.getRefreshRateSeconds(context);
        final int pollingPeriodMinutes;

        if (oneOff) {
            pollingPeriodMinutes = Settings.getPollingPeriodMinutes(context);
        } else {
            pollingPeriodMinutes = a.getMaxTime() - a.getMinTime();
        }

        cal.add(Calendar.MINUTE, pollingPeriodMinutes);
        endTimeMillis = cal.getTimeInMillis();

        sleepMillis = refreshRateSeconds * 1000L;
        maxAttempts = Math.round((pollingPeriodMinutes * 60f) / refreshRateSeconds);
    }

    @Override
    public void run() {
        if (context == null) {
            return;
        }

        final Notification notification = createNotification(true, OFF);
        final Intent cancelIntent = new Intent(Constants.ACTION_CANCEL_POLLING, a.getUri(), context, PollingService.class);
        final PendingIntent cancelPendingIntent = PendingIntent.getService(context, 0, cancelIntent, 0);

        final Agency agency = a.getAgency();

        final String format = context.getText(R.string.ongoing_message).toString();
        final String maxAttemptsStr = Integer.toString(maxAttempts);

        int nTimeouts = 0;
        int nErrors = 0;
        CharSequence errorMsg = context.getText(R.string.nothing_found);

        try {
            try {
                for (int i = 1;
                         (System.currentTimeMillis() < endTimeMillis) && (Thread.currentThread() == runner) && (nTimeouts < MAX_CONSECUTIVE_TIMEOUTS) && (nErrors < MAX_ERRORS);
                         i++) {
                    //XXX instead of update, use progress bar.
                    final String latestEventInfo = MessageFormat.format(format, Integer.toString(i), maxAttemptsStr);
                    notification.setLatestEventInfo(context, a.getLabelOrStopName(), latestEventInfo, cancelPendingIntent);
                    notificationMgr.notify(a.getId(), notification);

                    final ArrayList<Vehicle> matches = new ArrayList<Vehicle>(3);

                    final Matcher m = new Matcher(a);

                    try {
                        final ArrayList<Vehicle> vehicles = agency.getVehicles(a.getStopCode());

                        for (final Vehicle t : vehicles) {
                            if (m.isInRange(t)) {
                                matches.add(t);
                            }
                        }

                        final int nMatches = matches.size();

                        if (nMatches > 0) {
                            notifyTimeToTransit();

                            final Intent intent = new Intent(Constants.ACTION_TIME_TO_GO, a.getUri());
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            intent.putExtra(Constants.N_MATCHES, nMatches);
                            context.startActivity(intent);

                            return;
                        } else {
                            // Reset timeout count since we got through.
                            nTimeouts = 0;
                        }
                    } catch (final DataRetrievalException e) {
                        switch (e.getMessageId()) {
                            case R.string.socket_timeout_exception:
                                errorMsg = e.getMessage();
                                nTimeouts++;

                                break;

                            case R.string.io_exception:
                            case R.string.sax_exception:default:
                                errorMsg = e.getMessage();
                                nErrors++;
                        }
                    }

                    Thread.sleep(sleepMillis);
                }
            } finally {
                if (context instanceof PollingService) {
                    final PollingService ps = (PollingService) context;
                    ps.signalEnd(this);
                }
            }

            synchronized (a) {
                final boolean hasErrors = (nErrors > 0) || (nTimeouts >= MAX_CONSECUTIVE_TIMEOUTS);

                notifyNothingFound(hasErrors, errorMsg);

                final Intent intent = new Intent(Constants.ACTION_NOTHING_FOUND, a.getUri());
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra(Constants.ERROR_FLAG, hasErrors);
                intent.putExtra(Constants.ERROR_MSG, errorMsg);
                context.startActivity(intent);
            }
        } catch (final InterruptedException e) {
            final Notification interruptedException = createNotification(false, RED);
            interruptedException.flags = Notification.FLAG_AUTO_CANCEL;
            interruptedException.setLatestEventInfo(context, a.getLabelOrStopName(), context.getText(R.string.interrupted_exception), viewPendingIntent);
            notificationMgr.cancel(a.getId());
            notificationMgr.notify(a.getId(), interruptedException);

            Thread.currentThread().interrupt();
        }
    }

    private void notifyTimeToTransit() {
        final Notification timeToTransit = createNotification(false, GREEN);
        timeToTransit.setLatestEventInfo(context, a.getLabelOrStopName(), context.getText(R.string.time_to_go), viewPendingIntent);
        notificationMgr.cancel(a.getId());
        notificationMgr.notify(a.getId(), timeToTransit);
    }

    private void notifyNothingFound(final boolean hasErrors, final CharSequence errorMsg) {
        final Notification nothingFound = createNotification(false, RED);

        if (hasErrors) {
            if (errorMsg != null) {
                nothingFound.setLatestEventInfo(context, a.getLabelOrStopName(), context.getText(R.string.nothing_found_dia_err), viewPendingIntent);
            } else {
                nothingFound.setLatestEventInfo(context, a.getLabelOrStopName(), null, viewPendingIntent);
            }
        } else {
            nothingFound.setLatestEventInfo(context, a.getLabelOrStopName(), context.getText(R.string.nothing_found), viewPendingIntent);
        }

        notificationMgr.cancel(a.getId());
        notificationMgr.notify(a.getId(), nothingFound);
    }

    private Notification createNotification(final boolean isOngoing, final int ledARGB) {
        final Notification notification = new Notification(R.drawable.ic_transit_alarm, a.getLabelOrStopName(), System.currentTimeMillis());

        if (isOngoing) {
            notification.flags |= Notification.FLAG_ONGOING_EVENT;
            notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
        }

        if (ledARGB != OFF) {
            notification.ledARGB = ledARGB;
            notification.ledOnMS = 300;
            notification.ledOffMS = 1000;
            notification.flags |= Notification.FLAG_SHOW_LIGHTS;
        }

        return notification;
    }

    public Alarm getAlarm() {
        return a;
    }
}
